魔鬼隐藏在细节之中
--我看Intel CT ADE

我设计koodoo语言的动因当然是解决实际问题,在几年前我面临着复杂多变的IVR应用,复杂多变的流程、不同的后台数据库,甚至牵涉到各种运算。于是试图设计一种全新的脚本语言,设想CTI的大部分工作均由这个单一的语言来完成。设计时不由自主的受到各种流行语言的影响,比如C/C++, JAVA, 当然还有同样是脚本语言的Python和AWK。可能是自己的孤陋寡闻,当时对Intel CT ADE并不了解,直到最近好几个朋友告诉我,说你的平台和Intel CT ADE很相似,于是认真地坐下来,审视并研究这个工业巨头的软件平台产品,并试图写下一点个人的看法。

当然我写这篇东西,里面的观点很多朋友肯定不能同意,我自己在动笔之前也很犹豫,毕竟作为一种语言的设计者,对自己的东西会有所偏爱,拿自己的优点去比较对方的缺点,在立场上很难做到公正。但反过来看,正因为我是语言的设计者,也许我能看到对方在设计上的某些历史痕迹,看到他的取舍。

也许CTI的市场太小,市场上并没有中立的权威的机构去做这些评测性的工作,这是令人遗憾的。

下面的评论主要针对Intel的VOS语言(也叫AD语言),其中文文档来自:
http://www.ctiforum.com/train/intel/tech/tech01_001_00.htm


一、语法结构

据介绍:

"AD语言是一种高效率的类似于C或C++的电话专用脚本语言。一个只有14行AD语言代码的应用(或三个流程图标)可能需要500多行的C语言或者C++的代码。特别为Intel的Dialogic硬件和电话开发而设计的 AD语言编译成P代码。它允许所有可用的硬件端口运行在单一的线程上(Windows 2000)。它具有特别的高性能,因为它相对于大多数多线程的C或C++应用降低了管理费用而性能最优。"

VOS语言试图以C/C++的语法为蓝本,这当然是正确的选择,问题是VOS语言自行其是的语法和C/C++相距万里:

C语法:

  for(..)
  {
    ...
  }

  while(...)
  {
    ...
  }
		

VOS语法:

  for(..)
    ...
  endfor

  while(..)
    ...
  endwhile

  if(...)
    ...
  endif
		

凭空多出这许多无用的关键字。

语句注释,VOS语言只有单行注释,而且使用单一分隔字符'#'号--C/C++语言是'//', 没有多行注释对于屏蔽大段代码简直太辛苦了。

其实VOS语言如此设计当然是为了简化编译系统, 'endif'比'}'更容易和'if'匹配, 单个字符的'#'比两个字符的'//'更容易处理,何况'/'还是除号运算符。编译系统是简单了,但用户感觉别扭。

VOS语言不支持switch(), case这种多分支开关语句的语法结构,在IVR的语音菜单中,开关语句非常有用。

也许VOS语言的设计者认为有if语句就够了,也许switch(), case这种多分支开关语句对VOS语言的编译系统来说,太复杂了。


二、运算符

1. 字符串拼接:

'&'号, 这个有点像早期的BASIC语言,但我认为不如大多数语言的'+'号来得直观。而且使用'+'号可以进行更复杂的运算。在VOS语言字符串拼接的优先级最高,仿佛说“先加减后乘除”,不符合大多数人的习惯。

2. 逻辑运算符号

C/C++采用直观的符号: ==, !=, &&, ||, <, >

VOS语言则采用关键字: eq, not, and, or; 但不相等比较还是用符号:<>

更为别扭的是,数值比较和字符串比较居然使用不同的符号:

  1. 比较数值相等: eq
  2. 比较字符串相等: streq
  3. 比较字符串不相等: strneq

太不简明了, 而且缺少内部的一致性。


三、变量和数组

变量和数组在VOS语言中有很大的限制,不利于开发更大、更复杂的应用。

1. 没有局部变量只有全局变量

VOS语言所有的变量必须在程序的开始定义:

dec
  var v1...
  ...
enddec
		

大量使用全局变量的弊端大家都知道,每个全局变量相当于一个接口,在函数内部没有局部变量,等于增大了模块的藕合度。

对于大型项目而言,高藕合度的模块代码相当难以维护。这点在现代的软件工程理论中已经是定论。

实际上现代的脚本语言如Python, 变量不需要先定义,需要时赋值、使用。

2. 变量在内部表示为字符串,不直接支持浮点运算

变量在内部表示为字符串大概是借用Unix下的脚本语言AWK语言的设计,尤其方便和外部动态库的接口,因为这样一来,外部DLL的函数只需要处理单一的C语言数据类型: char *.

但是,在VOS语言里变量有长度的限制,而且必须事先指定:

dec
   var x : 2;
enddec
		

对于这个限制,他们解释道:

  1. "为什么要对变量限定最大长度?
  2. 为什么VOS不运行动态分配内存?
  3. 主要是因为呼叫处理系统常常在无人维护的状态下运行数天、数星期或者数个月。
  4. 设计VOS时避免使用内存动态分配是为了防止内存碎片和内存不足等问题,这也是为VOS设计一种新的语言而不使用现存的语言的原因。"

对于这个解释,我不能认同,现代的操作系统已经不是当年简陋的带有640K内存限制的DOS,已经不是"昔日吴下阿蒙", 现在我们使用的是带有大容量内存的快速机器,Windows2000、Unix等现代操作系统也有着高效稳定的内存调度算法。

如果真像他们所说的,那像C++这种频繁分配、释放内存的语言,甚至JAVA、C#等这些带垃圾回收的语言,岂不是根本没有办法使用到关键系统之中去?

实际上很多电信级的应用都是用C/C++写成,而且大量地使用动态内存分配。采用JAVA语言的J2EE更是企业级运算的标准。

在VOS语言的内部不直接支持浮点运算,这是个问题,毕竟浮点运算还是很常见的。当然问题的根源在于其变量在内部都表示为字符串,VOS的编译器很难处理。

还好,作为弥补,CT ADE提供了外部库,可以进行浮点运算,但这样就不是很直观了,因为一个简单运算都要变成函数调用,如加法要调用fp_add函数, 减法要调用fp_sub等等。

3. 数组

VOS语言中数组这样定义:

dec
  var Arr[1..10] : 8;
enddec
		

必须指定下标和长度,而且下标最多只能在0..255的范围内,一个程序中定义的数组个数也不能超过255。

这么多的限制,必然影响数组的使用。

而且从库函数来看,没有充分利用数组的灵活性,在这里自夸一下,说说Koodoo语言的数组。

Koodoo语言的数组当然没有上面那些限制,它甚至不需要定义,下标也不会越界,而且在库函数一级有神奇的功能:

    m = 0;
    AnlyStr("U.S.A., CN, JAP, HK", ",", m);  // 分解串到数组
    m自动变成一个数组,这个数组有4个成员:
    m[0] - "U.S.A."
    m[1] - "CN"
    m[2] - "JAP"
    m[3] - "HK"
		

当然还有读取文本文件的每一行到数组,等等。


四、函数

VOS语言的函数定义,使用两个关键字'func'和'endfunc', 比如:

func add(arg1,arg2)
   return arg1+arg2;
endfunc
		

VOS语言函数的最大问题是没有局部变量,这个在前面变量部分已经讨论过,此外,对于多参数返回,VOS语言没有提供引用传递参数的办法,也许设计者认为既然变量都是全局的,所有的变量都能穿透到函数,也就不需要引用传递了。


五、库函数印象

VOS语言的库函数在信令接续、语音控制等方面很细也很全面,几乎所有的Dialogic的API函数都有对应的VOS库函数,这对于电信级别的应用开发非常方便。但是有利也有弊,对于一般应用则嫌之烦琐和复杂,换一种说法就是“封装的太薄”。

其它方面的库函数,可以说乏善可陈,这也许和它的数据类型不丰富以及语言机制有关系,也和它的封装风格有关系。

当然,因为它允许外部动态库,而外部库基本上可以不受VOS语言的限制,所以有一些功能较为强大的外部函数可供使用。


六、异曲同工的设计

话说回来,VOS语言的有些设计我还是比较认同,因为Koodoo语言也有相同的设计,也是很多朋友认为VOS语言与Koodoo语言比较相似的原因。

归纳起来, 有:

1. 脚本在单一的线程上运行,编程模式简单

2. 常量符号定义, 都是使用const关键字

3. 都支持文件包含, 使用#include语句

4. 支持外部动态库调用(外部函数),构成3种函数调用的方式:

  1. 1). 系统内部函数
  2. 2). 用户自定义函数
  3. 3). 外部动态库函数

不过,两者在细节上还是有差别的,比如对于外部动态库,Koodoo语言提供了装载语句,可以任何时候装载动态库; 而VOS在配置文件中说明,在程序开始时候装载。

对于用户自定义函数,两者都可以递归调用。

5. 支持任务(线路)间通讯

VOS语言支持: 信号量、消息和全局变量
Koodoo语言支持: 消息队列和全局变量
因为两者对任务的概念有所不同,所以在细节上也不一样。

6. 支持挂断事件处理块

   onhangup # Hang-up processing
    TrunkDisconnect(),
    restart;
   endonhangup
		

Koodoo语言则提供了OnDisconn()系统函数。

更进一步,Koodoo语言还提供了OnSysQuit()系统函数, 响应系统退出事件.


本文主要从脚本编程语言上指出VOS语言的不足,其它诸如编程、发布环境,系统架构等其它方面未作涉及。

总体上看来,Intel CT ADE的VOS是有着DOS时代遗迹的“古老”语言,对于构造大型的IVR应用存在不小的限制。


bluesen 2004.7 于深圳